package calpi.database;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import calpi.Aluno;
import calpi.Curso;
import calpi.Disciplina;
import java.io.FileOutputStream;
import java.io.FileInputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.EOFException;
import java.util.ArrayList;
import java.util.LinkedList;

/**
 * Classe responsável pela gravação e recuperação dos dados dos alunos
 * @author Sem iNick
 */
public class AlunosConnector extends DataBaseConnector<Aluno> {

    /**
     * Arquivo padrão usado na leitura e gravação dos registros. (alunos.rdb)
     */
    public static final File DEFAULT_FILE = new File("alunos.rdb");

    //Usado para seprar os registros
    private static final char RECORD_SEPARADOR = '\u0014';

    //Usado para verificar se ainda há mais disciplinas para ler
    private static final char DISCIPLINA_SEPARADOR = '\u0012';

    private DataInputStream inStream;

    //Usados para a recuperação da informações
    CursosConnector cursosDB;
    DisciplinasConnector disciDB;

    /**
     * Cria uma conector para ser usado para a gravação e leitura dos dados do alunos
     * com o arquivo padrão (alunos.rdb).
     * @param cCon Connector que será usado para obter informações dos cursos
     * @param dCon Connector que será usado para obter informações da disciplinas
     * @throws NullPointerException Se algum connector for nulo
     * @throws FileNotFoundException Se o arquivo não existir e não puder ser criado
     * @throws IOException Se ocorrer algum tipo de erro E/S
     */
    public AlunosConnector(CursosConnector cCon, DisciplinasConnector dCon)
            throws FileNotFoundException, IOException {

        this(DEFAULT_FILE, cCon, dCon);
    }

    /**
     * Cria uma conector para ser usado para a gravação dos dados do alunos
     * com o caminho passado.
     * @param file Path para o arquivo que será usado com entrada e saída de dados
     * @param cCon Connector que será usado para obter informações dos cursos
     * @param dCon Connector que será usado para obter informações da disciplinas
     * @throws NullPointerException Se algum connector for nulo
     * @throws FileNotFoundException Se o arquivo não existir e não puder ser criado
     * @throws IOException Se ocorrer algum tipo de erro E/S
     */
    public AlunosConnector(String file, CursosConnector cCon,
                            DisciplinasConnector dCon)
                            throws FileNotFoundException, IOException {

        this(new File(file), cCon, dCon);
    }

    /**
     * Cria uma conector para ser usado para a gravação dos dados do alunos
     * com o caminho passado.
     * @param file Objeto relacionado ao arquivo que será usado com entrada
     * e saída de dados
     * @param cCon Connector que será usado para obter informações dos cursos
     * @param dCon Connector que será usado para obter informações da disciplinas
     * @throws NullPointerException Se algum connector for nulo
     * @throws FileNotFoundException Se o arquivo não existir e não puder ser criado
     * @throws IOException Se ocorrer algum tipo de erro E/S
     */
    public AlunosConnector(File file, CursosConnector cCon,
                            DisciplinasConnector dCon)
                            throws FileNotFoundException, IOException {

        this(file, file, cCon, dCon);
    }

    /**
     * Cria uma conector para ser usado para a gravação dos dados do alunos
     * com o caminho passado.
     * @param fileIn Caminho do arquivo que será usado com entrada
     * @param fileOut Caminho do arquivo que será usado com saída
     * @param cCon Connector que será usado para obter informações dos cursos
     * @param dCon Connector que será usado para obter informações da disciplinas
     * @throws NullPointerException Se algum connector for nulo
     * @throws FileNotFoundException Se o arquivo não existir e não puder ser criado
     * @throws IOException Se ocorrer algum tipo de erro E/S
     */
    public AlunosConnector(String fileIn, String fileOut, CursosConnector cCon,
                            DisciplinasConnector dCon)
                throws FileNotFoundException, IOException {

        this(new File(fileIn), new File(fileOut), cCon, dCon);
    }

    /**
     * Cria uma conector para ser usado para a gravação dos dados do alunos
     * com o caminho passado.
     * @param fileIn Objeto relacionado ao arquivo que será usado com entrada
     * @param fileOut Objeto relacionado ao arquivo que será usado com saída
     * @param cCon Connector que será usado para obter informações dos cursos
     * @param dCon Connector que será usado para obter informações da disciplinas
     * @throws NullPointerException Se algum connector for nulo
     * @throws FileNotFoundException Se o arquivo não existir e não puder ser criado
     * @throws IOException Se ocorrer algum tipo de erro E/S
     */
    public AlunosConnector(File fileIn, File fileOut, CursosConnector cCon,
                            DisciplinasConnector dCon)
                            throws FileNotFoundException, IOException {

        super(fileIn, fileOut);
        inStream = new DataInputStream(new FileInputStream(getFileIn()));
        setCursosConnector(cCon);
        setDisciplinasConnector(dCon);
    }

    /**
     * Getter para o connector dos cursos
     * @return Connector das informações dos cursos
     */
    public CursosConnector getCursosConnector() {
        
        return cursosDB;
    }

    /**
     * Setter para o connector que será usadado para obter informações dos cursos
     * @param cursosDB Connector para os dados dos cursos
     * @throws NullPointerException Se o novo connector for nulo
     */
    public void setCursosConnector(CursosConnector cursosDB) {

        if (cursosDB == null) {

            throw new NullPointerException("Connector cursos nulo");
        }

        this.cursosDB = cursosDB;
    }

    /**
     * Getter para o connector das disciplinas
     * @return Connector das informações das disciplinas
     */
    public DisciplinasConnector getDisciplinasConnector() {
        return disciDB;
    }

    /**
     * Setter para o connector que será usadado para obter informações das
     * disciplinas
     * @param disciDB Connector para os dados das disciplinas
     * @throws NullPointerException Se o novo connector for nulo
     */
    public void setDisciplinasConnector(DisciplinasConnector disciDB) {

        if (disciDB == null) {

            throw new NullPointerException("Connector disciplinas nulo");
        }
        
        this.disciDB = disciDB;
    }


    /**
     * Garante que os streams serão fechados após não existir referência ao objeto
     * @throws IOException Se ocorrer algum erro no fechamento dos streams
     */
    @Override
    protected void finalize() throws IOException {

        closeConnection();
    }

    /**
     * Fecha a conexão.
     * @throws IOException Se ocorrer algum erro na hora de fechar a conexão
     */
    public void closeConnection() throws IOException {

        if (inStream != null) {

            inStream.close();
        }
    }

    /**
     * Lê os dados do arquivo de entrada e coloca no buffer de entrada apagando
     * qualquer informação pre-existente no buffer de entrada.
     * @throws IOException Se ocorrer algum erro E/S
     */
    @Override
    public synchronized void readFully() throws IOException {

        LinkedList<Aluno> buffer = new LinkedList<Aluno>();
        //Usado para armazenar as disciplinas lidas para o aluno
        LinkedList<Disciplina> disci = new LinkedList<Disciplina>();
        //Usada para quando o curso não for encontrado por algum motivo
        Curso notFound = new Curso(Integer.MAX_VALUE, "Curso não encontrado");
        String nome = null;
        int mat = 0;
        String ender = null;
        Curso curso = null;
        int per = 0;
        
        try {

            while (true) {

                nome = inStream.readUTF();
                mat = inStream.readInt();
                ender = inStream.readUTF();
                Curso search = cursosDB.getCursoById(inStream.readInt());
                curso = (search == null
                        ? notFound
                        : search);
                per = inStream.readInt();

                //Verificar se há disciplinas para ler
                while (inStream.readChar() != RECORD_SEPARADOR) {

                    Disciplina d = disciDB.getDisciplinaByCod(inStream.readInt());
                    if (d != null) {

                        disci.add(d);
                    }
                }

                if (disci.size() != 0) {

                    Disciplina[] d = new Disciplina[disci.size()];
                    disci.toArray(d);
                    buffer.add(new Aluno(nome, mat, ender, curso, per, d));
                } else {

                    buffer.add(new Aluno(nome, mat, ender, curso, per));
                }

                disci.clear();
            }
        } catch (EOFException eofe) {

            //End of file
            //Do nothing =}
        }
                
        bufferIn = new Aluno[buffer.size()];
        buffer.toArray(bufferIn);
        bufferOut = new ArrayList<Aluno>(buffer);
    }

    /**
     * Escreve os dados contidos no buffer de saída pra o arquivo de saída na
     * ordem em que eles se encontram. Se você quiser escrevê-los usando alguma
     * ordem, organize o buffer de entrada antes de escrevê-lo.
     * @throws IOException
     * @see DataBaseConnector#sortBufferOut() 
     */
    @Override
    public synchronized void writeBuffer() throws IOException {

        DataOutputStream outStream = new DataOutputStream(new FileOutputStream(getFileOut()));
        for (Aluno aluno : bufferOut) {

            outStream.writeUTF(aluno.getNome());
            outStream.writeInt(aluno.getMatricula());
            outStream.writeUTF(aluno.getEndereco());
            outStream.writeInt(aluno.getCurso().getId());
            outStream.writeInt(aluno.getPeriodo());
            Disciplina[] disc = aluno.getDisciplinas();
            for (Disciplina disciplina : disc) {

                outStream.writeChar(DISCIPLINA_SEPARADOR);
                outStream.writeInt(disciplina.getCodigo());
            }            
            
            outStream.writeChar(RECORD_SEPARADOR);
        }

        //Atualiza o buffer de entrada
        bufferIn = new Aluno[bufferOut.size()];
        bufferOut.toArray(bufferIn);
        outStream.flush();
        outStream.close();
    }
}
